package info.angelloznicoud.ircdl.bean;

import info.angelloznicoud.ircdl.bot.IrcDownloaderBot;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.log4j.Logger;
import org.jibble.pircbot.IrcException;
import org.jibble.pircbot.NickAlreadyInUseException;

/**
 * Download Manager manages downloads by creating bot and
 * giving to them what file to download.
 *
 * @author ANGELLOZ-NICOUD Sébastien
 *
 * @version 1.0
 */
public class DownloadManager implements Runnable {

	private static final Logger LOG = Logger.getLogger(DownloadManager.class);

	private FileManager fileManager = null;

	private Map<String, IrcDownloaderBot> bots;

	private Map<String, Integer> botsNamesIdx;

	private long sleepDelay = 5000;

	private int maxDownload = 1;

	private String[] botNames = null;

	private File output = null;

	private boolean botVerbose;

	private boolean sigInt;

	public DownloadManager(FileManager fileManager, long sleepDelay, String[] botNames,
			File output, boolean verbose, int maxDownload) {
		this.fileManager = fileManager;
		this.bots        = new HashMap<String, IrcDownloaderBot>();
		this.sleepDelay  = sleepDelay;
		this.botNames    = botNames;
		this.output      = output;
		this.botVerbose  = verbose;
		this.sigInt      = false;
		this.maxDownload = maxDownload;
		this.botsNamesIdx = new HashMap<String, Integer>();

		LOG.info("Starting download manager with " + this.maxDownload + " max simultaneous download(s)" +
				" and " + (this.sleepDelay / 1000) + " sec(s) of refresh rate.");
	}

	/**
	 * If all download from the list are done.
	 *
	 * @return <code>true</code> if yes, <code>false</code> otherwise.
	 */
	public boolean finished() {
		return !this.fileManager.hasFilesToDowload()
				&& !atLeastOnBotIsDownloading();
	}

	/**
	 * Run the main method of the thread.
	 */
	@Override
	public void run() {
		LOG.info("Download started ! ");

		while (!this.finished() && !this.sigInt) {

			DownloadMessage nextFileToDownload = this.fileManager.getNextMessage(this.maxDownload);

			if (nextFileToDownload != null) {
				LOG.debug("Next file to download : " + nextFileToDownload);

				IrcDownloaderBot bot;

				if ((bot = this.bots.get(nextFileToDownload.getServer())) == null) {
					LOG.info("Creating bot for server " + nextFileToDownload.getServer());

					this.botsNamesIdx.put(nextFileToDownload.getServer(), Integer.valueOf(0));

					bot = new IrcDownloaderBot(nextFileToDownload.getServer(),
							this.botNames[this.botsNamesIdx.get(nextFileToDownload.getServer())],
							this.output,
							this.botVerbose);

					boolean error = false;

					try {
						bot.connect();
						bot.joinChan(nextFileToDownload.getChannel());
						this.bots.put(nextFileToDownload.getServer(), bot);
						bot.startDownload(nextFileToDownload);
					} catch (NickAlreadyInUseException e) {
						LOG.error("NickAlreadyInUseException : ", e);

						int idxName = this.botsNamesIdx.get(nextFileToDownload.getServer());

						this.botsNamesIdx.put(nextFileToDownload.getServer(), (++idxName) % this.botNames.length);

						error = true;
					} catch (IOException e) {
						LOG.error("IOException : ", e);
						this.bots.remove(nextFileToDownload.getServer());

						error = true;
					} catch (IrcException e) {
						LOG.error("IrcException : ", e);
						this.bots.remove(nextFileToDownload.getServer());

						error = true;
					}

					// (re)put the download file to fileManager.
					if (error) {
						this.fileManager.retryDownload(nextFileToDownload);
					}
				} else {
					if (!bot.isDownloading()) {
						bot.joinChan(nextFileToDownload.getChannel());
						bot.startDownload(nextFileToDownload);
					} else {
						// This case should not happen
						// But just in case...
						LOG.warn("A bot seems download but a file has been get by the fileManager !");
						LOG.warn("Something is wrong, maybe restart the program...");
						this.fileManager.retryDownload(nextFileToDownload);
					}
				}
			}

			try {
				Thread.sleep(this.sleepDelay);
			} catch (InterruptedException e) {
				LOG.warn("InterruptedException : ", e);
			}
		}

		LOG.info("Download list reach an end");

		// Control+C killing has already disconnect bots.
		if (!this.sigInt) {
			for (IrcDownloaderBot bot : this.bots.values()) {
				LOG.info("Disconnecting bot : " + bot.getName());
				bot.disconnect();
			}
		}

		LOG.info("Download ended ! ");
	}

	/**
	 * Send xdcc cancel message to all currently downloading
	 * bot and disconnect them from the server.
	 */
	public void sendSigInt() {
		for (IrcDownloaderBot bot : this.bots.values()) {
			if (bot.isDownloading()) {
				LOG.warn("Disconnecting bot " + bot.getName() + " from " + bot.getServer());
				bot.sendXdccCancel();
				bot.disconnect();
			}
		}

		this.sigInt = true;
	}

	/**
	 * Get a list of download name and progress.
	 *
	 * @return an array of string with download name, progress (in %) and download rate (in ko/s).
	 */
	public String[] getCurrentDownloads() {
		List<String> res = new ArrayList<String>();
		String progress = null;

		for (IrcDownloaderBot bot : this.bots.values()) {
			progress = bot.getDownloadProgress();

			if (progress != null) {
				res.add(progress);
			}
		}

		return res.toArray(new String[res.size()]);
	}

	/**
	 * Check if at least one bot is downloading a file.
	 *
	 * @return <code>true</code> if yes, <code>false</code> otherwise.
	 */
	public boolean atLeastOnBotIsDownloading() {
		for (IrcDownloaderBot bot : this.bots.values()) {
			if (bot.isDownloading()) {
				return true;
			}
		}

		return false;
	}
}
